// Copyright (C) 2010  Joan Puigcerver Perez, <joapuipe@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#include <Database.hpp>

#include <ctime>
#include <queue>

Database::Database(std::ostream& logf, ost::Mutex& logm)
  : logfile(logf), log_mutex(logm)
{
}


/* Calcula la distància entre dues persones, utilitzant un filtre. */
Database::DistanceResult Database::distance(const Person source, 
					    const Person target, 
					    const Filter filter) const
{

  mysqlpp::Connection * 
    connection = db_create_and_connect(Config::get_DBName(), 
				       Config::get_DBHost(),
				       Config::get_DBUser(),
				       Config::get_DBPass());

  size_t people_size = people.size(connection) + 1;
  size_t movies_size = movies.size(connection) + 1;
  
  std::queue<Person_ID> queue;
  std::vector<Person_ID> distances( people_size );
  std::vector<LinkPair> predecesors( people_size );
  std::vector<bool> seen_people( people_size );
  std::vector<bool> seen_movies( movies_size );

  for(size_t i = 0; i < people_size; ++i) {
    distances[i] = 0xFFFFFFFF;
    seen_people[i] = false; 
  }

  for(uint32_t i = 0; i < movies_size; ++i) {
    seen_movies[i] = false;
  }
      
  distances[source.id()] = 0;
  seen_people[source.id()] = true;
  queue.push( source );
  
  /* BFS */
  while( !seen_people[target.id()] && !queue.empty() ) {
    const Person pers_a = queue.front();
    queue.pop();

    LinkList pers_a_links = pers_a.links(connection);
    for(LinkList::const_iterator mov_it = pers_a_links.begin(); 
	mov_it != pers_a_links.end(); ++mov_it) { 
      // Pel·lícules de la persona A
      const Movie movie = Movie(mov_it->movie());
      if ( seen_movies[movie.id()] || !filter.pass(*mov_it) || !filter.pass(movie) ) continue; // peli vista o link o peli no passen filtres.

      LinkList movie_links = movie.links(connection);
      for(LinkList::const_iterator pers_it = movie_links.begin(); 
	  pers_it != movie_links.end(); ++pers_it) {   // Persones en la pel·lícula
	const Person pers_b = Person(pers_it->person());
	if ( seen_people[pers_b.id()] || !filter.pass(*pers_it) ) continue; // persona vista o link no passa filtres.

	distances[pers_b.id()] = distances[ pers_a.id() ] + 1;     
	predecesors[pers_b.id()] = LinkPair(*mov_it, *pers_it);
	seen_people[pers_b.id()] = true;              // persona vista
	queue.push( pers_b );
      }

      seen_movies[movie.id()] = true;  // quan hem visitat tots els actors, hem acabat amb la peli
    }
  }

  /* Processem la sortida. */
  LinkPairList path;
  if ( !seen_people[target.id()] ) {
    db_disconnect_and_delete(&connection);
    return DistanceResult(0xFFFFFFFF, path);
  } else {
    uint32_t src = source.id();
    uint32_t cur = target.id();
    while ( cur != src ) {
      path.push_back( predecesors[cur] );
      cur = predecesors[cur].first.person();
    }

    path.reverse();    

    db_disconnect_and_delete(&connection);
    return DistanceResult(distances[target.id()], path);
  }  
}

// void Database::PureBFS(const Person * source, std::vector<uint32_t>& distances, const uint32_t max_dist, const Filter filter) const
// {
//   std::queue<const Person*> queue;
//   std::vector<bool> seen_people( people.size() );
//   std::vector<bool> seen_movies( movies.size() );

//   for(uint32_t i = 0; i < people.size(); ++i) {
//     distances[i] = 0xFFFFFFFF;
//     seen_people[i] = false; 
//   }

//   for(uint32_t i = 0; i < movies.size(); ++i) {
//     seen_movies[i] = false;
//   }
  
//   distances[source->id()] = 0;
//   seen_people[source->id()] = true;
//   queue.push( source );
  
//   /* BFS */
//   while( !queue.empty() ) {
//     const Person * pers_a = queue.front();
//     queue.pop();

//     for(std::list<const Link*>::const_iterator mov_it = pers_a->begin(); mov_it != pers_a->end(); ++mov_it) { // Pel·lícules de la persona A
//       const Movie * movie = (*mov_it)->movie();
//       if ( seen_movies[movie->id()] || !filter.pass(*mov_it) || !filter.pass(movie) ) continue; // peli vista o link o peli no passen filtres.

//       for(std::list<const Link*>::const_iterator pers_it = movie->begin(); pers_it != movie->end(); ++pers_it) {   // Persones en la pel·lícula
// 	const Person * pers_b = (*pers_it)->person();
// 	if ( seen_people[pers_b->id()] || !filter.pass(*pers_it) ) continue; // persona vista o link no passa filtres.

// 	distances[pers_b->id()] = distances[ pers_a->id() ] + 1;     
// 	seen_people[pers_b->id()] = true;              // persona vista
// 	if ( distances[pers_b->id()] < max_dist ) 
// 	  queue.push( pers_b );
//       }
      
//       seen_movies[movie->id()] = true;  // quan hem visitat tots els actors, hem acabat amb la peli
//     }
//   }
// }

// float Database::center(const Person * source, const Filter filter) const
// {
//   std::vector<uint32_t> distances( people.size() );
//   PureBFS(source, distances, 0xFFFFFFFF, filter);

//   float sum = 0.0f; uint32_t noninf = 0;
//   for(size_t i = 0; i < distances.size(); ++i)
//     if ( i != source->id() && distances[i] != 0xFFFFFFFF ) {
//       sum += (float)distances[i];
//       ++noninf;
//     }
  
//   if ( noninf == 0 ) return 0x7F800000;
//   else return sum/(float)noninf;
// }

// std::list<const Person *> Database::distance_n(const Person * source, const uint32_t dist, 
// 					       const Filter filter) const
// {
//   std::vector<uint32_t> distances( people.size() );
//   PureBFS(source, distances, dist, filter);

//   std::list<const Person*> people_at_dist_n;
//   for(size_t i = 0; i < distances.size(); ++i)
//     if ( distances[i] == dist )
//       people_at_dist_n.push_back( people[i] );
  
//   return people_at_dist_n;
// } 

// std::pair< bool, std::list<const Person *> > Database::search_person(const std::string& name) const
// {
//   return people.search( name );
// }
